# load required libraries

# to use harry potter dataset
# devtools::install_github("bradleyboehmke/harrypotter")
# devtools::install_github("quanteda/quanteda.sentiment")
# devtools::install_github("quanteda/quanteda.corpora")

library(quanteda)
library(readtext)
library(corpus)
library(tidyverse)
library(stringr)
library(tidytext)
library(harrypotter)
library(dplyr)
library(quanteda.sentiment)
library(vader)
library(caret)
library(reshape2)


require(quanteda)
require(quanteda.corpora)
require(quanteda.sentiment)
#library("quanteda", warn.conflicts = FALSE, quietly = TRUE)

1. Step: Load Data & Lexicons

lsd
Dictionary object with 4 key entries.
Polarities: pos = "positive", "neg_negative"; neg = "negative", "neg_positive" 
- [negative]:
  - a lie, abandon*, abas*, abattoir*, abdicat*, aberra*, abhor*, abject*, abnormal*, abolish*, abominab*, abominat*, abrasiv*, absent*, abstrus*, absurd*, abus*, accident*, accost*, accursed* [ ... and 2,838 more ]
- [positive]:
  - ability*, abound*, absolv*, absorbent*, absorption*, abundanc*, abundant*, acced*, accentuat*, accept*, accessib*, acclaim*, acclamation*, accolad*, accommodat*, accomplish*, accord, accordan*, accorded*, accords [ ... and 1,689 more ]
- [neg_positive]:
  - best not, better not, no damag*, no no, not ability*, not able, not abound*, not absolv*, not absorbent*, not absorption*, not abundanc*, not abundant*, not acced*, not accentuat*, not accept*, not accessib*, not acclaim*, not acclamation*, not accolad*, not accommodat* [ ... and 1,701 more ]
- [neg_negative]:
  - not a lie, not abandon*, not abas*, not abattoir*, not abdicat*, not aberra*, not abhor*, not abject*, not abnormal*, not abolish*, not abominab*, not abominat*, not abrasiv*, not absent*, not abstrus*, not absurd*, not abus*, not accident*, not accost*, not accursed* [ ... and 2,840 more ]
reviews
twitter
parlvote

2. Step: Perform Sentiment Analysis

Normalize Scores

# DATA NORMALIZATION
# Normalize data via minimun/maximum normalization, either by scaling values from 0 to 1 or -1 to 1
# 
# Arg:
#   x: input values (e.g. column of data frame)
#   
# Returns: 
#   normalized data


# min/max normalization from 0 to 1
normalize <- function(x, na.rm = TRUE){
  return((x-min(x)) / (max(x)-min(x)))}

# min/max normalization from -1 to 1
normalize2 <- function(x, na.rm = TRUE){
  return(2* ((x - min(x)) / (max(x)-min(x)))-1)}

Calculate Sentiment Scores

3. Step: Calculate Statistics

Convert data into ternary format

# Convert final values into ternary (1 = positive, 0 = neutral, -1 = negative) format for evaluation and comparison
# 
# Arg:
#   df: input data frame that contains the columns to be converted into ternary format
#   to_change: column names that should be converted into ternary format
#   gold: goldstandard, i.e. rating, which has to be changed to ternary format
#   
# Returns: 
#   data frame with converted values

get_binary <- function(df, to_change, gold){
  df %>% 
    mutate_at(to_change, function(x){
      # mutate values greater than 0 to 1 (positive), equal to 0 to 0 (neutral) and smaller than 0 to -1 (negative)
      case_when(x > 0 ~ 1, x < 0 ~ -1, x == 0 ~ 0)}) %>% 
    
    mutate_at(gold, function(x){
      # if x is between 0-5, mutate values greater than 0 to 1, equal to 0 to 0 and smaller than 0 to -1
      case_when(between(x,0,5) & x > 3 ~ 1, between(x,0,5) & x == 3 ~ 0, between(x,0,5) & x < 3 ~ -1,
        x == "Positive" ~ 1, x == "Negative" ~ -1, x == "Neutral" ~ 0)})
}

reviews_binary <- get_binary(reviews_sentiment, c("afinn","vader","lsd"), "rating")
twitter_binary <- get_binary(twitter_sentiment, c("afinn","vader","lsd"), "rating")
Warnung: Problem with `mutate()` column `rating`.
ℹ `rating = (function (x) ...`.
ℹ NAs durch Umwandlung erzeugt
Warnung: Problem with `mutate()` column `rating`.
ℹ `rating = (function (x) ...`.
ℹ NAs durch Umwandlung erzeugt
Warnung: Problem with `mutate()` column `rating`.
ℹ `rating = (function (x) ...`.
ℹ NAs durch Umwandlung erzeugt
parlvote_binary <- get_binary(parlvote_sentiment, c("afinn","vader","lsd"), "rating")

Calculate Accuracy, Precision, Recall

# Function to get statistics (accuracy, precision, recall) of data frame for specific lexicon
#
# Arg: 
#   df: data frame that we want statistics of
#   lexicon: binary/ternary lexicon that should be evaluated
#
# Returns:
#   overall accuracy score
#   data frame with statistics
  
get_statistics <- function(df, lexicon){
  
  # create confusion matrix (via caret library) to get statistics of data frame
  cm <- confusionMatrix(factor(df[[lexicon]]), factor(df$rating), mode="prec_recall")
  
  acc <- (as.data.frame(cm$overall))["Accuracy",]
  stats <- as.data.frame(cm$byClass)
  results <- list(acc, stats)
  
  # check how to return two statements
  return(results)
}

reviews_afinn.acc <- get_statistics(reviews_binary, "afinn")[[1]][1]
reviews_afinn.stats <- get_statistics(reviews_binary, "afinn")[2]

reviews_lsd.acc <- get_statistics(reviews_binary, "lsd")[[1]][1]
reviews_lsd.stats <- get_statistics(reviews_binary, "lsd")[2]

reviews_vader.acc <- get_statistics(reviews_binary, "vader")[[1]][1]
reviews_vader.stats <- get_statistics(reviews_binary, "vader")[2]

4. Step: Plot Data

# plot columns
reviews_dfm <- melt(head(reviews_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

reviews_plot <- ggplot(reviews_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                ggtitle("Reviews Sentiments")


twitter_dfm <- melt(head(twitter_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

twitter_plot <- ggplot(twitter_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                ggtitle("Twitter Sentiments")

parlvote_dfm <- melt(head(parlvote_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

parlvote_plot <- ggplot(parlvote_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
                facet_wrap(~ variable, ncol = 1, scales="free_y")+
                ggtitle("ParlVote Sentiments")

#parlvote_norm_dfm <- melt(head(parlvote_sentiment,50)[,c('id','afinn_norm','lsd_norm','vader_norm')],id.vars = 1)

#parlvote_norm_plot <- ggplot(parlvote_norm_dfm,aes(x = id,y = value)) + 
              #  geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
               # facet_wrap(~ variable, ncol = 1, scales="free_y")+
                #ggtitle("ParlVote Sentiments")


reviews_plot

twitter_plot

parlvote_plot

#parlvote_norm_plot

Plot important words

# optional

Ranking of texts per lexicon

# sort per corpus and per tool
reviews_afinn.sort <- reviews_sentiment[order(reviews_sentiment$afinn, decreasing=TRUE),]

reviews_lsd.sort <- reviews_sentiment[order(reviews_sentiment$lsd, decreasing=TRUE),]

reviews_vader.sort <- reviews_sentiment[order(reviews_sentiment$vader, decreasing=TRUE),]


reviews_afinn.sort
reviews_lsd.sort
reviews_vader.sort

5. Step: Evaluation (Notes)

Comparison Groups: - compare binary (normalized) versions - each lexicon and data set - compare continuous (normalized) versions - each lexicon and data set - compare top n words per sentiment per tool? (= contribution to sentiment) - compare ranking (Stede’s idea): - rank texts per corpus and compare across tools -> is order similar? - tool’s performance - how is performance across domains? - is binary more accurate than continuous scoring? -

ADDITIONAL

Harry Potter - Dataset

# load harry potter dataset 
titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")

books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
           goblet_of_fire, order_of_the_phoenix, half_blood_prince,
           deathly_hallows)
  
series <- tibble()

for(i in seq_along(titles)) {
        
        clean <- tibble(chapter = seq_along(books[[i]]),
                        text = books[[i]]) %>%
             unnest_tokens(word, text) %>%
             mutate(book = titles[i]) %>%
             select(book, everything())

        series <- rbind(series, clean)
}

series$book <- factor(series$book, levels = rev(titles))

series
#book_groups <- series %>% group_by(book, chapter)
# tokenize hp1
#hp1_tokenized <- tokens_tolower(tokens(philosophers_stone, remove_punct = TRUE)) 
afinn_hp <- series %>%
       # group_by(book, chapter) %>% # add word for single word scores 
        #inner_join(get_sentiments("afinn")) %>%
        inner_join(textstat_valence(series$tokens,afinn)$sentiment) %>%
      #  group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
       # summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  #%>%
Warnung: Unknown or uninitialised column: `tokens`.
Fehler: textstat_valence() only works on character, corpus, dfm, tokens objects.

Harry Potter - AFINN Lexicon

afinn_hp2 <- series %>%
        group_by(book, chapter) %>% # add word for single word scores 
        inner_join(get_sentiments("afinn")) %>%
        group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
        summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  %>%
        ggplot(aes(chapter, sentiment, fill = book)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x")

afinn_hp2

#ggsave(plot = afinn, width = 15, height = 15, dpi = 300, filename = "afinn_hp_mean.png")

Lexicoder: HP

# select only the "negative" and "positive" categories
#data_dictionary_LSD2015_pos_neg <- data_dictionary_LSD2015[1:2]
#hp1_lsd <- tokens_lookup(hp1_tokenized, dictionary = data_dictionary_LSD2015_pos_neg)

polarity(data_dictionary_LSD2015) <- 
  list(pos = c("positive", "neg_negative"), neg = c("negative", "neg_positive"))

hp1_lsd <- textstat_polarity(hp1_tokenized, data_dictionary_LSD2015)

hp1_lsd_tokens <- tokens_lookup(hp1_tokenized, data_dictionary_LSD2015, nested_scope = "dictionary", exclusive = FALSE)
hp1_lsd.df <- as.data.frame.matrix(hp1_lsd)
hp1_lsd.df$chapter <- 1:nrow(hp1_lsd.df)

plot <- ggplot(hp1_lsd, aes(x =hp1_lsd.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - Lexicoder")

AFINN: HP

hp1_afinn <- textstat_valence(hp1_tokenized, afinn, normalize="dictionary")

hp1_afinn.df <- as.data.frame.matrix(hp1_afinn)
hp1_afinn.df$chapter <- 1:nrow(hp1_afinn.df)

plot <- ggplot(hp1_afinn.df, aes(x =hp1_afinn.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - AFINN")

VADER: HP

get_vader(philosophers_stone[1])

hp1_vader <- vader_df(philosophers_stone)
hp1_vader$chapter <- 1:nrow(hp1_vader)

plot <- ggplot(hp1_vader, aes(x =chapter, y=compound)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-5.0, 5.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - VADER")

QUANTEDA.SENTIMENT

AFINN: HP

# Work with quanteda.sentiment on HP corpus:
# convert tibble to dataframe
series.df <- as.data.frame(series)

# tokenize books
series_tokenized <- series.df %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
series_tokenized$afinn <- textstat_valence(series_tokenized$tokens, afinn)$sentiment

# replace all 0 values with na
series_tokenized[series_tokenized == 0] <- NA

series_tokenized %>%
  group_by(book, chapter) %>% # group df by book and chapter to get sentiment per chapter
  summarise(sentiment = mean(afinn, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(chapter, sentiment, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("AFINN HP")

Lexicoder: HP

# Work with quanteda.sentiment on HP corpus:
# apply lexicoder lexicon
series$lsd <- textstat_polarity(tokens(series$text), data_dictionary_LSD2015)$sentiment 

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder HP")
plot 

Vader: HP

# apply vader lexicon to all HP books
series$vader <- vader_df(series$text)$compound

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("VADER HP")
plot 

REVIEWS DATASET

# load dataset
reviews <- readtext("datasets/goodreads_reviews_children_2.json", text_field = "review_text")

# convert to dataframe
reviews.df <- as.data.frame(reviews)

# add doc_id (i.e. according to index)
reviews.df$doc_id <- 1:nrow(reviews.df)

Sample Dataset

# get random sample of 50 reviews 
reviews_sample <- reviews.df[sample(1:nrow(reviews.df), 50,
   replace=FALSE),]

# get first 50 rows of data 
reviews_50 <- head(reviews.df,50)
reviews_50 = subset(reviews_50, select = c(doc_id,text,rating))

Get Translations of Dataset

# either via corpus 
reviews.corpus <- corpus(reviews)
docvars(reviews.corpus, "language") <- textcat(reviews.corpus)
reviews_en <- corpus_subset(reviews.corpus, language == "english", drop_docid = TRUE)

# or via dataframe logic
reviews.df$language <- textcat(reviews.df$text)

Sentiment Analysis on Reviews Dataset

AFINN

# Afinn
# tokenize 
reviews_tokenized <- reviews_50 %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
reviews_tokenized$afinn <- textstat_valence(reviews_tokenized$tokens, afinn)$sentiment

# replace all 0 values with na
reviews_tokenized[reviews_tokenized == 0] <- NA

# calculate mean scores for tokens per doc
afinn_scores <- reviews_tokenized %>%
  group_by(doc_id) %>% # group df by doc_id to get mean sentiment score
  summarise(total = mean(afinn, na.rm = TRUE)) #%>% # calculate mean w/o regarding na values

# add afinn scores to df 
reviews_50$afinn <- afinn_scores$total

# different version to get plot 
reviews_tokenized %>%
  group_by(doc_id) %>% # group df by book and chapter to get sentiment per chapter
  #reviews_tokenized$sentiment = mean(afinn, na.rm = TRUE) %>%
  summarise(sentiment = mean(afinn, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(doc_id, sentiment, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("AFINN Reviews")

LEXICODER

# apply lexicoder lexicon
reviews_50$lsd <- textstat_polarity(tokens(reviews_50$text), data_dictionary_LSD2015)$sentiment 
#series.df <- as.data.frame(series)

plot <- ggplot(reviews_50, aes(doc_id, lsd, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder Reviews")
plot 

Vader

reviews_50$vader <- vader_df(reviews_50$text)$compound

plot <- ggplot(reviews_50, aes(doc_id, vader, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Vader Reviews")
plot 
get_statistics <- function(df) {
  statistics <- data.frame(matrix(ncol=4, nrow=0))
  x <- c("accuracy", "precision", "recall", "F1")
  colnames(statistics) <- x
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    TN <- confusion_matrix[1]
    FN <- confusion_matrix[2]
    FP <- confusion_matrix[3]
    TP <- confusion_matrix[4]
  
    # calculate statistics
    precision <- TP/(TP+FP)
    accuracy <- (TP+TN)/(TP+TN+FP+FN)
    recall <- TP/(TP+FN)
    F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    output <- c(accuracy,precision, recall, F1)
    statistics[lex,] = rbind(statistics[[lex]], output)
    }
    
  return(statistics)
  
}

get_statistics(test)

Test Functions

# complete function to get lexicon scores
get_sentiment <- function(data, lexicons){
  
  for(lex in lexicons){
    if(lex == "afinn"){
      
    data$afinn <- textstat_valence(tokens(data$text), afinn, normalize="dictionary")$sentiment
    }
    
    if(lex == "lsd"){
    data$lsd<- textstat_polarity(tokens(data$text), data_dictionary_LSD2015)$sentiment
    }
    
    if(lex == "vader"){
    data$vader <- vader_df(data$text)$compound
    }
  }
  data[is.na(data)] <- 0
  return(data)
}

reviews_sentiment <- get_sentiment(reviews, c("afinn","lsd", "vader"))
twitter_sentiment <- get_sentiment(twitter, c("afinn","lsd", "vader"))
parlvote_sentiment <- get_sentiment(parlvote, c("afinn","lsd", "vader"))

parlvote_sentiment
#write.csv(parlvote_sentiment, "datasets/parlvote_sentiment.csv", row.names = FALSE)

```

LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBTZW50aW1lbnQgVG9vbHMgYWNyb3NzIERvbWFpbnMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KYGBge3J9CiMgbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKCiMgdG8gdXNlIGhhcnJ5IHBvdHRlciBkYXRhc2V0CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJicmFkbGV5Ym9laG1rZS9oYXJyeXBvdHRlciIpCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicXVhbnRlZGEvcXVhbnRlZGEuY29ycG9yYSIpCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQpsaWJyYXJ5KGNvcnB1cykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShoYXJyeXBvdHRlcikKbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkocmVzaGFwZTIpCgoKcmVxdWlyZShxdWFudGVkYSkKcmVxdWlyZShxdWFudGVkYS5jb3Jwb3JhKQpyZXF1aXJlKHF1YW50ZWRhLnNlbnRpbWVudCkKI2xpYnJhcnkoInF1YW50ZWRhIiwgd2Fybi5jb25mbGljdHMgPSBGQUxTRSwgcXVpZXRseSA9IFRSVUUpCmBgYAoKIyAxLiBTdGVwOiBMb2FkIERhdGEgJiBMZXhpY29ucwpgYGB7cn0KIyBsb2FkIGRhdGFzZXRzCnJldmlld3MgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfcmV2aWV3LnJkcyIpCnR3aXR0ZXIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfdHdpdHRlci5yZHMiKQpwYXJsdm90ZSA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL3JlZF9wYXJsX3ZvdGUucmRzIikKCiMgbG9hZCBsZXhpY29ucwphZmlubiA8LSBkYXRhX2RpY3Rpb25hcnlfQUZJTk4KbHNkIDwtIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1Cgpsc2QKYGBgCgpgYGB7cn0KcmV2aWV3cwp0d2l0dGVyCnBhcmx2b3RlCmBgYAojIDIuIFN0ZXA6IFBlcmZvcm0gU2VudGltZW50IEFuYWx5c2lzCiMjIyBOb3JtYWxpemUgU2NvcmVzCmBgYHtyfQojIERBVEEgTk9STUFMSVpBVElPTgojIE5vcm1hbGl6ZSBkYXRhIHZpYSBtaW5pbXVuL21heGltdW0gbm9ybWFsaXphdGlvbiwgZWl0aGVyIGJ5IHNjYWxpbmcgdmFsdWVzIGZyb20gMCB0byAxIG9yIC0xIHRvIDEKIyAKIyBBcmc6CiMgICB4OiBpbnB1dCB2YWx1ZXMgKGUuZy4gY29sdW1uIG9mIGRhdGEgZnJhbWUpCiMgICAKIyBSZXR1cm5zOiAKIyAgIG5vcm1hbGl6ZWQgZGF0YQoKCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZyb20gMCB0byAxCm5vcm1hbGl6ZSA8LSBmdW5jdGlvbih4LCBuYS5ybSA9IFRSVUUpewogIHJldHVybigoeC1taW4oeCkpIC8gKG1heCh4KS1taW4oeCkpKX0KCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZyb20gLTEgdG8gMQpub3JtYWxpemUyIDwtIGZ1bmN0aW9uKHgsIG5hLnJtID0gVFJVRSl7CiAgcmV0dXJuKDIqICgoeCAtIG1pbih4KSkgLyAobWF4KHgpLW1pbih4KSkpLTEpfQpgYGAKCiMjIyBDYWxjdWxhdGUgU2VudGltZW50IFNjb3JlcwpgYGB7cn0KIyBDYWxjdWxhdGUgc2VudGltZW50IHNjb3JlcyBmb3IgZGlmZmVyZW50IGxleGljb25zIGFuZCBpbnB1dCBkYXRhIGZyYW1lcwojIAojIEFyZzoKIyAgIGRhdGE6IGlucHV0IGRhdGEgZnJhbWUKIyAgIGxleGljb25zOiBuYW1lcyBvZiBsZXhpY29ucyB0aGF0IHNob3VsZCBiZSB1c2VkIGZvciBzZW50aW1lbnQgc2NvcmluZwojICAgbm9ybWFsaXplOiBUUlVFIG9yIEZBTFNFLCB0byBvcHRpb25hbGx5IG5vcm1hbGl6ZSBzZW50aW1lbnQgc2NvcmVzCiMgICAKIyBSZXR1cm5zOiAKIyAgIERhdGEgZnJhbWUgd2l0aCAobm9ybWFsaXplZCkgc2VudGltZW50IHNvcmVzIGZvciBjaG9zZW4gbGV4aWNvbnMKCmdldF9zZW50aW1lbnQgPC0gZnVuY3Rpb24oZGYsIGxleGljb25zLCBub3JtYWxpemUgPSBUUlVFKXsKICAKICAjIGZvciBlYWNoIGxleGljb24sIGdldCBzZW50aW1lbnQgc2NvcmVzIGFuZCBzYXZlIHNjb3JlcyBpbiBuZXcgY29sdW1uIG9mIGRhdGEgZnJhbWUKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIAogICAgaWYobGV4ID09ICJhZmlubiIpewogICAgZGYkYWZpbm4gPC0gcm91bmQodGV4dHN0YXRfdmFsZW5jZSh0b2tlbnMoZGYkdGV4dCksIGFmaW5uLCBub3JtYWxpemU9ImRpY3Rpb25hcnkiKSRzZW50aW1lbnQsMyl9CiAgICAKICAgIGlmKGxleCA9PSAibHNkIil7CiAgICBkZiRsc2Q8LSByb3VuZCh0ZXh0c3RhdF9wb2xhcml0eSh0b2tlbnMoZGYkdGV4dCksIGxzZCkkc2VudGltZW50LDMpfQogICAgCiAgICBpZihsZXggPT0gInZhZGVyIil7CiAgICBkZiR2YWRlciA8LSByb3VuZCh2YWRlcl9kZihkZiR0ZXh0KSRjb21wb3VuZCwzKX0KICB9CiAgCiAgIyBub3JtYWxpemUgc2VudGltZW50IHNjb3JlcyBpZiBUUlVFLCBWQURFUiBzY29yZXMgYXJlIGFscmVhZHkgbm9ybWFsaXplZAogIGlmKG5vcm1hbGl6ZT09VFJVRSl7CiAgICBkZiRhZmlubiA8LSByb3VuZChub3JtYWxpemUyKGRmJGFmaW5uKSwgMykKICAgIGRmJGxzZCA8LSByb3VuZChub3JtYWxpemUyKGRmJGxzZCksIDMpCiAgfQogIAogICMgc2V0IHNlbnRpbWVudCBzY29yZXMgdG8gMCBpZiBOQQogIGRmW2lzLm5hKGRmKV0gPC0gMAogIAogIHJldHVybihkZikKfQoKcmV2aWV3c19zZW50aW1lbnQgPC0gZ2V0X3NlbnRpbWVudChyZXZpZXdzLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQp0d2l0dGVyX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXIsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCnBhcmx2b3RlX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHBhcmx2b3RlLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQoKIyB0byBzYXZlIGRhdGEgZnJhbWUgYXMgY3N2IGZpbGUKI3dyaXRlLmNzdihwYXJsdm90ZV9zZW50aW1lbnQsICJkYXRhc2V0cy9wYXJsdm90ZV9zZW50aW1lbnQuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCgpyZXZpZXdzX3NlbnRpbWVudApgYGAKCiMgMy4gU3RlcDogQ2FsY3VsYXRlIFN0YXRpc3RpY3MKIyMjIENvbnZlcnQgZGF0YSBpbnRvIHRlcm5hcnkgZm9ybWF0CmBgYHtyfQojIENvbnZlcnQgZmluYWwgdmFsdWVzIGludG8gdGVybmFyeSAoMSA9IHBvc2l0aXZlLCAwID0gbmV1dHJhbCwgLTEgPSBuZWdhdGl2ZSkgZm9ybWF0IGZvciBldmFsdWF0aW9uIGFuZCBjb21wYXJpc29uCiMgCiMgQXJnOgojICAgZGY6IGlucHV0IGRhdGEgZnJhbWUgdGhhdCBjb250YWlucyB0aGUgY29sdW1ucyB0byBiZSBjb252ZXJ0ZWQgaW50byB0ZXJuYXJ5IGZvcm1hdAojICAgdG9fY2hhbmdlOiBjb2x1bW4gbmFtZXMgdGhhdCBzaG91bGQgYmUgY29udmVydGVkIGludG8gdGVybmFyeSBmb3JtYXQKIyAgIGdvbGQ6IGdvbGRzdGFuZGFyZCwgaS5lLiByYXRpbmcsIHdoaWNoIGhhcyB0byBiZSBjaGFuZ2VkIHRvIHRlcm5hcnkgZm9ybWF0CiMgICAKIyBSZXR1cm5zOiAKIyAgIGRhdGEgZnJhbWUgd2l0aCBjb252ZXJ0ZWQgdmFsdWVzCgpnZXRfYmluYXJ5IDwtIGZ1bmN0aW9uKGRmLCB0b19jaGFuZ2UsIGdvbGQpewogIGRmICU+JSAKICAgIG11dGF0ZV9hdCh0b19jaGFuZ2UsIGZ1bmN0aW9uKHgpewogICAgICAjIG11dGF0ZSB2YWx1ZXMgZ3JlYXRlciB0aGFuIDAgdG8gMSAocG9zaXRpdmUpLCBlcXVhbCB0byAwIHRvIDAgKG5ldXRyYWwpIGFuZCBzbWFsbGVyIHRoYW4gMCB0byAtMSAobmVnYXRpdmUpCiAgICAgIGNhc2Vfd2hlbih4ID4gMCB+IDEsIHggPCAwIH4gLTEsIHggPT0gMCB+IDApfSkgJT4lIAogICAgCiAgICBtdXRhdGVfYXQoZ29sZCwgZnVuY3Rpb24oeCl7CiAgICAgICMgaWYgeCBpcyBiZXR3ZWVuIDAtNSwgbXV0YXRlIHZhbHVlcyBncmVhdGVyIHRoYW4gMCB0byAxLCBlcXVhbCB0byAwIHRvIDAgYW5kIHNtYWxsZXIgdGhhbiAwIHRvIC0xCiAgICAgIGNhc2Vfd2hlbihiZXR3ZWVuKHgsMCw1KSAmIHggPiAzIH4gMSwgYmV0d2Vlbih4LDAsNSkgJiB4ID09IDMgfiAwLCBiZXR3ZWVuKHgsMCw1KSAmIHggPCAzIH4gLTEsCiAgICAgICAgeCA9PSAiUG9zaXRpdmUiIH4gMSwgeCA9PSAiTmVnYXRpdmUiIH4gLTEsIHggPT0gIk5ldXRyYWwiIH4gMCl9KQp9CgpyZXZpZXdzX2JpbmFyeSA8LSBnZXRfYmluYXJ5KHJldmlld3Nfc2VudGltZW50LCBjKCJhZmlubiIsInZhZGVyIiwibHNkIiksICJyYXRpbmciKQp0d2l0dGVyX2JpbmFyeSA8LSBnZXRfYmluYXJ5KHR3aXR0ZXJfc2VudGltZW50LCBjKCJhZmlubiIsInZhZGVyIiwibHNkIiksICJyYXRpbmciKQpwYXJsdm90ZV9iaW5hcnkgPC0gZ2V0X2JpbmFyeShwYXJsdm90ZV9zZW50aW1lbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSwgInJhdGluZyIpCgpgYGAKIyMjIENhbGN1bGF0ZSBBY2N1cmFjeSwgUHJlY2lzaW9uLCBSZWNhbGwKYGBge3J9CiMgRnVuY3Rpb24gdG8gZ2V0IHN0YXRpc3RpY3MgKGFjY3VyYWN5LCBwcmVjaXNpb24sIHJlY2FsbCkgb2YgZGF0YSBmcmFtZSBmb3Igc3BlY2lmaWMgbGV4aWNvbgojCiMgQXJnOiAKIyAgIGRmOiBkYXRhIGZyYW1lIHRoYXQgd2Ugd2FudCBzdGF0aXN0aWNzIG9mCiMgICBsZXhpY29uOiBiaW5hcnkvdGVybmFyeSBsZXhpY29uIHRoYXQgc2hvdWxkIGJlIGV2YWx1YXRlZAojCiMgUmV0dXJuczoKIyAgIG92ZXJhbGwgYWNjdXJhY3kgc2NvcmUKIyAgIGRhdGEgZnJhbWUgd2l0aCBzdGF0aXN0aWNzCiAgCmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmLCBsZXhpY29uKXsKICAKICAjIGNyZWF0ZSBjb25mdXNpb24gbWF0cml4ICh2aWEgY2FyZXQgbGlicmFyeSkgdG8gZ2V0IHN0YXRpc3RpY3Mgb2YgZGF0YSBmcmFtZQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IoZGZbW2xleGljb25dXSksIGZhY3RvcihkZiRyYXRpbmcpLCBtb2RlPSJwcmVjX3JlY2FsbCIpCiAgCiAgYWNjIDwtIChhcy5kYXRhLmZyYW1lKGNtJG92ZXJhbGwpKVsiQWNjdXJhY3kiLF0KICBzdGF0cyA8LSBhcy5kYXRhLmZyYW1lKGNtJGJ5Q2xhc3MpCiAgcmVzdWx0cyA8LSBsaXN0KGFjYywgc3RhdHMpCiAgCiAgIyBjaGVjayBob3cgdG8gcmV0dXJuIHR3byBzdGF0ZW1lbnRzCiAgcmV0dXJuKHJlc3VsdHMpCn0KCnJldmlld3NfYWZpbm4uYWNjIDwtIGdldF9zdGF0aXN0aWNzKHJldmlld3NfYmluYXJ5LCAiYWZpbm4iKVtbMV1dWzFdCnJldmlld3NfYWZpbm4uc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19iaW5hcnksICJhZmlubiIpWzJdCgpyZXZpZXdzX2xzZC5hY2MgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19iaW5hcnksICJsc2QiKVtbMV1dWzFdCnJldmlld3NfbHNkLnN0YXRzIDwtIGdldF9zdGF0aXN0aWNzKHJldmlld3NfYmluYXJ5LCAibHNkIilbMl0KCnJldmlld3NfdmFkZXIuYWNjIDwtIGdldF9zdGF0aXN0aWNzKHJldmlld3NfYmluYXJ5LCAidmFkZXIiKVtbMV1dWzFdCnJldmlld3NfdmFkZXIuc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19iaW5hcnksICJ2YWRlciIpWzJdCmBgYAoKIyA0LiBTdGVwOiBQbG90IERhdGEgCmBgYHtyfQojIHBsb3QgY29sdW1ucwpyZXZpZXdzX2RmbSA8LSBtZWx0KGhlYWQocmV2aWV3c19zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgpyZXZpZXdzX3Bsb3QgPC0gZ2dwbG90KHJldmlld3NfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUmV2aWV3cyBTZW50aW1lbnRzIikKCgp0d2l0dGVyX2RmbSA8LSBtZWx0KGhlYWQodHdpdHRlcl9zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgp0d2l0dGVyX3Bsb3QgPC0gZ2dwbG90KHR3aXR0ZXJfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiVHdpdHRlciBTZW50aW1lbnRzIikKCnBhcmx2b3RlX2RmbSA8LSBtZWx0KGhlYWQocGFybHZvdGVfc2VudGltZW50LDUwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQoKcGFybHZvdGVfcGxvdCA8LSBnZ3Bsb3QocGFybHZvdGVfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH4gdmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUGFybFZvdGUgU2VudGltZW50cyIpCgojcGFybHZvdGVfbm9ybV9kZm0gPC0gbWVsdChoZWFkKHBhcmx2b3RlX3NlbnRpbWVudCw1MClbLGMoJ2lkJywnYWZpbm5fbm9ybScsJ2xzZF9ub3JtJywndmFkZXJfbm9ybScpXSxpZC52YXJzID0gMSkKCiNwYXJsdm90ZV9ub3JtX3Bsb3QgPC0gZ2dwbG90KHBhcmx2b3RlX25vcm1fZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAjICBnZW9tX2JhcihhZXMoZmlsbCA9IHZhcmlhYmxlKSxzdGF0ID0gImlkZW50aXR5Iixwb3NpdGlvbiA9ICJkb2RnZSIpKwogICAgICAgICAgICAgICAjIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICAjZ2d0aXRsZSgiUGFybFZvdGUgU2VudGltZW50cyIpCgoKcmV2aWV3c19wbG90CnR3aXR0ZXJfcGxvdApwYXJsdm90ZV9wbG90CiNwYXJsdm90ZV9ub3JtX3Bsb3QKYGBgCiMjIyBQbG90IGltcG9ydGFudCB3b3JkcwpgYGB7cn0KIyBvcHRpb25hbApgYGAKCiMjIyBSYW5raW5nIG9mIHRleHRzIHBlciBsZXhpY29uCmBgYHtyfQojIHNvcnQgcGVyIGNvcnB1cyBhbmQgcGVyIHRvb2wKcmV2aWV3c19hZmlubi5zb3J0IDwtIHJldmlld3Nfc2VudGltZW50W29yZGVyKHJldmlld3Nfc2VudGltZW50JGFmaW5uLCBkZWNyZWFzaW5nPVRSVUUpLF0KCnJldmlld3NfbHNkLnNvcnQgPC0gcmV2aWV3c19zZW50aW1lbnRbb3JkZXIocmV2aWV3c19zZW50aW1lbnQkbHNkLCBkZWNyZWFzaW5nPVRSVUUpLF0KCnJldmlld3NfdmFkZXIuc29ydCA8LSByZXZpZXdzX3NlbnRpbWVudFtvcmRlcihyZXZpZXdzX3NlbnRpbWVudCR2YWRlciwgZGVjcmVhc2luZz1UUlVFKSxdCgoKcmV2aWV3c19hZmlubi5zb3J0CnJldmlld3NfbHNkLnNvcnQKcmV2aWV3c192YWRlci5zb3J0CmBgYAoKIyA1LiBTdGVwOiBFdmFsdWF0aW9uIChOb3RlcykKLSBldmFsdWF0ZSBkYXRhLyBjb21wYXJlIGRhdGEKLSBhY2N1cmFjeSwgcHJlY2lzaW9uLCByZWNhbGwgCi0gcGVhcnNvbnMgY29lZmZpY2llbnQgCgpDb21wYXJpc29uIEdyb3VwczoKLSBjb21wYXJlIGJpbmFyeSAobm9ybWFsaXplZCkgdmVyc2lvbnMKICAtIGVhY2ggbGV4aWNvbiBhbmQgZGF0YSBzZXQKLSBjb21wYXJlIGNvbnRpbnVvdXMgKG5vcm1hbGl6ZWQpIHZlcnNpb25zCiAgLSBlYWNoIGxleGljb24gYW5kIGRhdGEgc2V0Ci0gY29tcGFyZSB0b3AgbiB3b3JkcyBwZXIgc2VudGltZW50IHBlciB0b29sPyAoPSBjb250cmlidXRpb24gdG8gc2VudGltZW50KQotIGNvbXBhcmUgcmFua2luZyAoU3RlZGUncyBpZGVhKToKICAtIHJhbmsgdGV4dHMgcGVyIGNvcnB1cyBhbmQgY29tcGFyZSBhY3Jvc3MgdG9vbHMgLT4gaXMgb3JkZXIgc2ltaWxhcj8KLSB0b29sJ3MgcGVyZm9ybWFuY2UKICAtIGhvdyBpcyBwZXJmb3JtYW5jZSBhY3Jvc3MgZG9tYWlucz8KICAtIGlzIGJpbmFyeSBtb3JlIGFjY3VyYXRlIHRoYW4gY29udGludW91cyBzY29yaW5nPwogIC0gCgoKCgoKCgoKCiMjIyBBRERJVElPTkFMCiMgSGFycnkgUG90dGVyIC0gRGF0YXNldApgYGB7cn0KIyBsb2FkIGhhcnJ5IHBvdHRlciBkYXRhc2V0IAp0aXRsZXMgPC0gYygiUGhpbG9zb3BoZXIncyBTdG9uZSIsICJDaGFtYmVyIG9mIFNlY3JldHMiLCAiUHJpc29uZXIgb2YgQXprYWJhbiIsCiAgICAgICAgICAgICJHb2JsZXQgb2YgRmlyZSIsICJPcmRlciBvZiB0aGUgUGhvZW5peCIsICJIYWxmLUJsb29kIFByaW5jZSIsCiAgICAgICAgICAgICJEZWF0aGx5IEhhbGxvd3MiKQoKYm9va3MgPC0gbGlzdChwaGlsb3NvcGhlcnNfc3RvbmUsIGNoYW1iZXJfb2Zfc2VjcmV0cywgcHJpc29uZXJfb2ZfYXprYWJhbiwKICAgICAgICAgICBnb2JsZXRfb2ZfZmlyZSwgb3JkZXJfb2ZfdGhlX3Bob2VuaXgsIGhhbGZfYmxvb2RfcHJpbmNlLAogICAgICAgICAgIGRlYXRobHlfaGFsbG93cykKICAKc2VyaWVzIDwtIHRpYmJsZSgpCgpmb3IoaSBpbiBzZXFfYWxvbmcodGl0bGVzKSkgewogICAgICAgIAogICAgICAgIGNsZWFuIDwtIHRpYmJsZShjaGFwdGVyID0gc2VxX2Fsb25nKGJvb2tzW1tpXV0pLAogICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gYm9va3NbW2ldXSkgJT4lCiAgICAgICAgICAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogICAgICAgICAgICAgbXV0YXRlKGJvb2sgPSB0aXRsZXNbaV0pICU+JQogICAgICAgICAgICAgc2VsZWN0KGJvb2ssIGV2ZXJ5dGhpbmcoKSkKCiAgICAgICAgc2VyaWVzIDwtIHJiaW5kKHNlcmllcywgY2xlYW4pCn0KCnNlcmllcyRib29rIDwtIGZhY3RvcihzZXJpZXMkYm9vaywgbGV2ZWxzID0gcmV2KHRpdGxlcykpCgpzZXJpZXMKI2Jvb2tfZ3JvdXBzIDwtIHNlcmllcyAlPiUgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikKIyB0b2tlbml6ZSBocDEKI2hwMV90b2tlbml6ZWQgPC0gdG9rZW5zX3RvbG93ZXIodG9rZW5zKHBoaWxvc29waGVyc19zdG9uZSwgcmVtb3ZlX3B1bmN0ID0gVFJVRSkpIApgYGAKYGBge3J9CmFmaW5uX2hwIDwtIHNlcmllcyAlPiUKICAgICAgICMgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgYWRkIHdvcmQgZm9yIHNpbmdsZSB3b3JkIHNjb3JlcyAKICAgICAgICAjaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lCiAgICAgICAgaW5uZXJfam9pbih0ZXh0c3RhdF92YWxlbmNlKHNlcmllcyR0b2tlbnMsYWZpbm4pJHNlbnRpbWVudCkgJT4lCiAgICAgICMgIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpICU+JSAjIGFkZCB3b3JkIGZvciBzaW5nbGUgd29yZCBzY29yZXMKICAgICAgICAjc3VtbWFyaXNlKHNlbnRpbWVudCA9IHN1bSh2YWx1ZSkpICU+JQogICAgICAgIyBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICAjJT4lCiAgICAgICAjIGdncGxvdChhZXMoY2hhcHRlciwgc2VudGltZW50LCBmaWxsID0gYm9vaykpICsKICAgICAgICMgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICMgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKQoKYWZpbm5faHAKYGBgCgoKIyMjIEhhcnJ5IFBvdHRlciAtIEFGSU5OIExleGljb24KYGBge3J9CmFmaW5uX2hwMiA8LSBzZXJpZXMgJT4lCiAgICAgICAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgYWRkIHdvcmQgZm9yIHNpbmdsZSB3b3JkIHNjb3JlcyAKICAgICAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpKSAlPiUKICAgICAgICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUgIyBhZGQgd29yZCBmb3Igc2luZ2xlIHdvcmQgc2NvcmVzCiAgICAgICAgI3N1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0odmFsdWUpKSAlPiUKICAgICAgICBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKGNoYXB0ZXIsIHNlbnRpbWVudCwgZmlsbCA9IGJvb2spKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpCgphZmlubl9ocDIKCiNnZ3NhdmUocGxvdCA9IGFmaW5uLCB3aWR0aCA9IDE1LCBoZWlnaHQgPSAxNSwgZHBpID0gMzAwLCBmaWxlbmFtZSA9ICJhZmlubl9ocF9tZWFuLnBuZyIpCmBgYAoKIyBMZXhpY29kZXI6IEhQCmBgYHtyfQojIHNlbGVjdCBvbmx5IHRoZSAibmVnYXRpdmUiIGFuZCAicG9zaXRpdmUiIGNhdGVnb3JpZXMKI2RhdGFfZGljdGlvbmFyeV9MU0QyMDE1X3Bvc19uZWcgPC0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTVbMToyXQojaHAxX2xzZCA8LSB0b2tlbnNfbG9va3VwKGhwMV90b2tlbml6ZWQsIGRpY3Rpb25hcnkgPSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNV9wb3NfbmVnKQoKcG9sYXJpdHkoZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpIDwtIAogIGxpc3QocG9zID0gYygicG9zaXRpdmUiLCAibmVnX25lZ2F0aXZlIiksIG5lZyA9IGMoIm5lZ2F0aXZlIiwgIm5lZ19wb3NpdGl2ZSIpKQoKaHAxX2xzZCA8LSB0ZXh0c3RhdF9wb2xhcml0eShocDFfdG9rZW5pemVkLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkKCmhwMV9sc2RfdG9rZW5zIDwtIHRva2Vuc19sb29rdXAoaHAxX3Rva2VuaXplZCwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUsIG5lc3RlZF9zY29wZSA9ICJkaWN0aW9uYXJ5IiwgZXhjbHVzaXZlID0gRkFMU0UpCmhwMV9sc2QuZGYgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgoaHAxX2xzZCkKaHAxX2xzZC5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfbHNkLmRmKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX2xzZCwgYWVzKHggPWhwMV9sc2QuZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gTGV4aWNvZGVyIikKYGBgCiMgQUZJTk46IEhQCmBgYHtyfQpocDFfYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZShocDFfdG9rZW5pemVkLCBhZmlubiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5IikKCmhwMV9hZmlubi5kZiA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeChocDFfYWZpbm4pCmhwMV9hZmlubi5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfYWZpbm4uZGYpCgpwbG90IDwtIGdncGxvdChocDFfYWZpbm4uZGYsIGFlcyh4ID1ocDFfYWZpbm4uZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gQUZJTk4iKQpgYGAKIyBWQURFUjogSFAKYGBge3J9CmdldF92YWRlcihwaGlsb3NvcGhlcnNfc3RvbmVbMV0pCgpocDFfdmFkZXIgPC0gdmFkZXJfZGYocGhpbG9zb3BoZXJzX3N0b25lKQpocDFfdmFkZXIkY2hhcHRlciA8LSAxOm5yb3coaHAxX3ZhZGVyKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX3ZhZGVyLCBhZXMoeCA9Y2hhcHRlciwgeT1jb21wb3VuZCkpICsKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKcGxvdCArIHlsaW0oLTUuMCwgNS4wKSArIGxhYnMoeT0ic2VudGltZW50IiwgeCA9ICJjaGFwdGVyIikgKyBnZ3RpdGxlKCJIUDEgLSBWQURFUiIpCmBgYAojIFFVQU5URURBLlNFTlRJTUVOVAojIEFGSU5OOiBIUApgYGB7cn0KIyBXb3JrIHdpdGggcXVhbnRlZGEuc2VudGltZW50IG9uIEhQIGNvcnB1czoKIyBjb252ZXJ0IHRpYmJsZSB0byBkYXRhZnJhbWUKc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKIyB0b2tlbml6ZSBib29rcwpzZXJpZXNfdG9rZW5pemVkIDwtIHNlcmllcy5kZiAlPiUKICB1bm5lc3RfdG9rZW5zKHRva2VucywgdGV4dCkKCiMgYXBwbHkgYWZpbm4gbGV4aWNvbgpzZXJpZXNfdG9rZW5pemVkJGFmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2Uoc2VyaWVzX3Rva2VuaXplZCR0b2tlbnMsIGFmaW5uKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpzZXJpZXNfdG9rZW5pemVkW3Nlcmllc190b2tlbml6ZWQgPT0gMF0gPC0gTkEKCnNlcmllc190b2tlbml6ZWQgJT4lCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhjaGFwdGVyLCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkFGSU5OIEhQIikKYGBgCiMgTGV4aWNvZGVyOiBIUCAgCmBgYHtyfQojIFdvcmsgd2l0aCBxdWFudGVkYS5zZW50aW1lbnQgb24gSFAgY29ycHVzOgojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnNlcmllcyRsc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKHNlcmllcyR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudCAKCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChzZXJpZXMsIGFlcyhjaGFwdGVyLCBsc2QsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBIUCIpCnBsb3QgCmBgYAoKIyBWYWRlcjogSFAKYGBge3J9CiMgYXBwbHkgdmFkZXIgbGV4aWNvbiB0byBhbGwgSFAgYm9va3MKc2VyaWVzJHZhZGVyIDwtIHZhZGVyX2RmKHNlcmllcyR0ZXh0KSRjb21wb3VuZAoKI3Nlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCnBsb3QgPC0gZ2dwbG90KHNlcmllcywgYWVzKGNoYXB0ZXIsIGxzZCwgZmlsbCA9IGJvb2spKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVkFERVIgSFAiKQpwbG90IApgYGAKCiMgUkVWSUVXUyBEQVRBU0VUCmBgYHtyfQojIGxvYWQgZGF0YXNldApyZXZpZXdzIDwtIHJlYWR0ZXh0KCJkYXRhc2V0cy9nb29kcmVhZHNfcmV2aWV3c19jaGlsZHJlbl8yLmpzb24iLCB0ZXh0X2ZpZWxkID0gInJldmlld190ZXh0IikKCiMgY29udmVydCB0byBkYXRhZnJhbWUKcmV2aWV3cy5kZiA8LSBhcy5kYXRhLmZyYW1lKHJldmlld3MpCgojIGFkZCBkb2NfaWQgKGkuZS4gYWNjb3JkaW5nIHRvIGluZGV4KQpyZXZpZXdzLmRmJGRvY19pZCA8LSAxOm5yb3cocmV2aWV3cy5kZikKYGBgCgojIyMgU2FtcGxlIERhdGFzZXQKYGBge3J9CiMgZ2V0IHJhbmRvbSBzYW1wbGUgb2YgNTAgcmV2aWV3cyAKcmV2aWV3c19zYW1wbGUgPC0gcmV2aWV3cy5kZltzYW1wbGUoMTpucm93KHJldmlld3MuZGYpLCA1MCwKICAgcmVwbGFjZT1GQUxTRSksXQoKIyBnZXQgZmlyc3QgNTAgcm93cyBvZiBkYXRhIApyZXZpZXdzXzUwIDwtIGhlYWQocmV2aWV3cy5kZiw1MCkKcmV2aWV3c181MCA9IHN1YnNldChyZXZpZXdzXzUwLCBzZWxlY3QgPSBjKGRvY19pZCx0ZXh0LHJhdGluZykpCmBgYAojIyMgR2V0IFRyYW5zbGF0aW9ucyBvZiBEYXRhc2V0IApgYGB7cn0KIyBlaXRoZXIgdmlhIGNvcnB1cyAKcmV2aWV3cy5jb3JwdXMgPC0gY29ycHVzKHJldmlld3MpCmRvY3ZhcnMocmV2aWV3cy5jb3JwdXMsICJsYW5ndWFnZSIpIDwtIHRleHRjYXQocmV2aWV3cy5jb3JwdXMpCnJldmlld3NfZW4gPC0gY29ycHVzX3N1YnNldChyZXZpZXdzLmNvcnB1cywgbGFuZ3VhZ2UgPT0gImVuZ2xpc2giLCBkcm9wX2RvY2lkID0gVFJVRSkKCiMgb3IgdmlhIGRhdGFmcmFtZSBsb2dpYwpyZXZpZXdzLmRmJGxhbmd1YWdlIDwtIHRleHRjYXQocmV2aWV3cy5kZiR0ZXh0KQpgYGAKCiMjIyBTZW50aW1lbnQgQW5hbHlzaXMgb24gUmV2aWV3cyBEYXRhc2V0CgojIyMjIEFGSU5OCmBgYHtyfQojIEFmaW5uCiMgdG9rZW5pemUgCnJldmlld3NfdG9rZW5pemVkIDwtIHJldmlld3NfNTAgJT4lCiAgdW5uZXN0X3Rva2Vucyh0b2tlbnMsIHRleHQpCgojIGFwcGx5IGFmaW5uIGxleGljb24KcmV2aWV3c190b2tlbml6ZWQkYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZShyZXZpZXdzX3Rva2VuaXplZCR0b2tlbnMsIGFmaW5uKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpyZXZpZXdzX3Rva2VuaXplZFtyZXZpZXdzX3Rva2VuaXplZCA9PSAwXSA8LSBOQQoKIyBjYWxjdWxhdGUgbWVhbiBzY29yZXMgZm9yIHRva2VucyBwZXIgZG9jCmFmaW5uX3Njb3JlcyA8LSByZXZpZXdzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShkb2NfaWQpICU+JSAjIGdyb3VwIGRmIGJ5IGRvY19pZCB0byBnZXQgbWVhbiBzZW50aW1lbnQgc2NvcmUKICBzdW1tYXJpc2UodG90YWwgPSBtZWFuKGFmaW5uLCBuYS5ybSA9IFRSVUUpKSAjJT4lICMgY2FsY3VsYXRlIG1lYW4gdy9vIHJlZ2FyZGluZyBuYSB2YWx1ZXMKCiMgYWRkIGFmaW5uIHNjb3JlcyB0byBkZiAKcmV2aWV3c181MCRhZmlubiA8LSBhZmlubl9zY29yZXMkdG90YWwKCiMgZGlmZmVyZW50IHZlcnNpb24gdG8gZ2V0IHBsb3QgCnJldmlld3NfdG9rZW5pemVkICU+JQogIGdyb3VwX2J5KGRvY19pZCkgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgI3Jldmlld3NfdG9rZW5pemVkJHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkgJT4lCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhkb2NfaWQsIHNlbnRpbWVudCwgZmlsbCA9IGRvY19pZCkpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAjZmFjZXRfd3JhcCh+IGRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJBRklOTiBSZXZpZXdzIikKYGBgCiMjIyMgTEVYSUNPREVSCmBgYHtyfQojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnJldmlld3NfNTAkbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KHRva2VucyhyZXZpZXdzXzUwJHRleHQpLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkkc2VudGltZW50IAojc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgbHNkLCBmaWxsID0gZG9jX2lkKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICNmYWNldF93cmFwKH4gZG9jX2lkLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBSZXZpZXdzIikKcGxvdCAKYGBgCiMjIyMgVmFkZXIKYGBge3J9CnJldmlld3NfNTAkdmFkZXIgPC0gdmFkZXJfZGYocmV2aWV3c181MCR0ZXh0KSRjb21wb3VuZAoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgdmFkZXIsIGZpbGwgPSBkb2NfaWQpKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgI2ZhY2V0X3dyYXAofiBkb2NfaWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVmFkZXIgUmV2aWV3cyIpCnBsb3QgCmBgYAoKYGBge3J9CmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgc3RhdGlzdGljcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTQsIG5yb3c9MCkpCiAgeCA8LSBjKCJhY2N1cmFjeSIsICJwcmVjaXNpb24iLCAicmVjYWxsIiwgIkYxIikKICBjb2xuYW1lcyhzdGF0aXN0aWNzKSA8LSB4CiAgbGV4MSA8LSAiYWZpbm5fYmluYXJ5IgogIGxleDIgPC0gImxzZF9iaW5hcnkiCiAgbGV4MyA8LSAidmFkZXJfYmluYXJ5IgogIGdvbGQgPC0gInJhdGluZ19iaW5hcnkiCiAgCiAgbGV4aWNvbnMgPC0gYyhsZXgxLGxleDIsbGV4MykKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWRmW1tnb2xkXV0sIFBSRURJQ1RFRD1kZltbbGV4XV0pCiAgICBUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCiAgICBGTiA8LSBjb25mdXNpb25fbWF0cml4WzJdCiAgICBGUCA8LSBjb25mdXNpb25fbWF0cml4WzNdCiAgICBUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCiAgCiAgICAjIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCiAgICBwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogICAgYWNjdXJhY3kgPC0gKFRQK1ROKS8oVFArVE4rRlArRk4pCiAgICByZWNhbGwgPC0gVFAvKFRQK0ZOKQogICAgRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCiAgCiAgICAjIGFkZCB0byB0YWJsZQogICAgb3V0cHV0IDwtIGMoYWNjdXJhY3kscHJlY2lzaW9uLCByZWNhbGwsIEYxKQogICAgc3RhdGlzdGljc1tsZXgsXSA9IHJiaW5kKHN0YXRpc3RpY3NbW2xleF1dLCBvdXRwdXQpCiAgICB9CiAgICAKICByZXR1cm4oc3RhdGlzdGljcykKICAKfQoKZ2V0X3N0YXRpc3RpY3ModGVzdCkKYGBgCgojIyMgVGVzdCBGdW5jdGlvbnMKYGBge3J9CmBgYHtyfQojIGNvbXBsZXRlIGZ1bmN0aW9uIHRvIGdldCBsZXhpY29uIHNjb3JlcwpnZXRfc2VudGltZW50IDwtIGZ1bmN0aW9uKGRhdGEsIGxleGljb25zKXsKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGlmKGxleCA9PSAiYWZpbm4iKXsKICAgICAgCiAgICBkYXRhJGFmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2UodG9rZW5zKGRhdGEkdGV4dCksIGFmaW5uLCBub3JtYWxpemU9ImRpY3Rpb25hcnkiKSRzZW50aW1lbnQKICAgIH0KICAgIAogICAgaWYobGV4ID09ICJsc2QiKXsKICAgIGRhdGEkbHNkPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKGRhdGEkdGV4dCksIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSRzZW50aW1lbnQKICAgIH0KICAgIAogICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgZGF0YSR2YWRlciA8LSB2YWRlcl9kZihkYXRhJHRleHQpJGNvbXBvdW5kCiAgICB9CiAgfQogIGRhdGFbaXMubmEoZGF0YSldIDwtIDAKICByZXR1cm4oZGF0YSkKfQoKcmV2aWV3c19zZW50aW1lbnQgPC0gZ2V0X3NlbnRpbWVudChyZXZpZXdzLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQp0d2l0dGVyX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXIsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCnBhcmx2b3RlX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHBhcmx2b3RlLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQoKcGFybHZvdGVfc2VudGltZW50CiN3cml0ZS5jc3YocGFybHZvdGVfc2VudGltZW50LCAiZGF0YXNldHMvcGFybHZvdGVfc2VudGltZW50LmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKYGBgCgoKCgoKCg==